#include "graph_support/TestLanguageIssues.h"

#include <vector>
#include <algorithm>
#include <sstream>
#include <string.h>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

#include "graph_support/attributes/LengthEdgeAttributes.h"

#include "graph_support/impls/vector/VectorGraph.h"

#include "graph_support/utils/test_my_graph/BoostTestUtils.h"
#include "graph_support/utils/CollectionsInit.h"
#include "graph_support/utils/PtrAccessOpSimulator.h"
#include "graph_support/utils/RTTIStorage.h"

#include "graph_support/fakes/BusinessType.h"


using namespace GRAPH_SUPPORT_NS::utils;

namespace TestStoreGraphVertexPointers_namespace
{

class IntHld
{
public:
	IntHld()
		: content( -1 )
	{
	}
	IntHld( int content )
		: content( content )
	{
	}
	int getContent() const
	{
		return content;
	}
	bool operator == ( const IntHld & that ) const
	{
		return content == that.content;
	}
private:
	int content;
};

class HashSupport
{
public:
	size_t operator () ( const IntHld & h ) const
	{
		return stdext::hash< int >() ( h.getContent() );
	}
};

}

void TestStoreGraphVertexPointers()
{
	using namespace GRAPH_SUPPORT_NS::attributes;
	using namespace GRAPH_SUPPORT_NS::impls;
	using namespace GRAPH_SUPPORT_NS::impls::vector;
	using namespace TestStoreGraphVertexPointers_namespace;

	typedef VectorGraphTmpl< IntHld, Length1EdgeAttributes, HashSupport > GraphI;
	typedef GraphI::GrVertex GrVertex;
	typedef GraphI::VertexIterator VertexIterator;

	const GrVertex vertices[] = {
			GrVertex( 12 ), GrVertex( -34 ), GrVertex( 56 ),
			GrVertex( -78 ), GrVertex( 90 ), };
	std::vector< GrVertex > verts = arr2vector< GrVertex >( vertices );
	BOOST_CHECK_EQUAL( 5, verts.size() );

	GraphI gr( verts );

	std::vector< int > contentVec;
	for( VertexIterator it = gr.getVerticesBeg(); it != gr.getVerticesEnd(); ++it )
	{
//		IntHld & vertexA = * it; -- error
//		IntHld * vertexA = & ( * it ); -- warning
		int contentA = ( * it ).getContent();
//		IntHld * vertexA = it.operator -> (); -- error
		int contentB = it->getContent();
		BOOST_CHECK_EQUAL( contentA, contentB );
		contentVec.push_back( contentA );
	}

	BOOST_CHECK_EQUAL( verts.size(), contentVec.size() );

	for ( int i = 0; i < verts.size(); i++ )
	{
		GrVertex vertex = verts[i];
		int content = contentVec[i];
		BOOST_CHECK_EQUAL( vertex.getContent(), content );
	}
}

void TestResolveTableType()
{
	{
	typedef ResolveTableType< const char *, int > Resolver;
	Resolver::Table t;
	std::vector< const char * > chVec =
			arr2vector< const char * >( ( const char * const[] )
				{ "123", "456", "789" } );
	BOOST_CHECK_EQUAL( 3, chVec.size() );

	Resolver::init( t, chVec.begin(), chVec.end() );
	t["123"] = 321;
	t["456"] = 654;
	t["789"] = 987;

	BOOST_CHECK_EQUAL( 321, t["123"] );
	BOOST_CHECK_EQUAL( 654, t["456"] );
	BOOST_CHECK_EQUAL( 987, t["789"] );

	BOOST_CHECK_EQUAL( sizeof( stdext::hash_map< const char *, int > ), sizeof( ResolveTableType< const char *, int >::Table ) );
	}
	{
	typedef ResolveTableType< int, int > Resolver;
	Resolver::Table t;
	std::vector< int > chVec = arr2vector< int >( ( const int[] ) { 123, 456, 789 } );
	BOOST_CHECK_EQUAL( 3, chVec.size() );

	Resolver::init( t, chVec.begin(), chVec.end() );
	t[123] = 321;
	t[456] = 654;
	t[789] = 987;

	BOOST_CHECK_EQUAL( 321, t[123] );
	BOOST_CHECK_EQUAL( 654, t[456] );
	BOOST_CHECK_EQUAL( 987, t[789] );

	BOOST_CHECK_EQUAL( sizeof( std::vector< int > ) + sizeof( int ), sizeof( Resolver::Table ) );
	}
	{
	typedef ResolveTableType< int, const char * > Resolver;
	Resolver::Table t;
	std::vector< int > chVec = arr2vector< int >( ( const int[] ) { 123, 456, 789 } );
	BOOST_CHECK_EQUAL( 3, chVec.size() );

	Resolver::init( t, chVec.begin(), chVec.end() );
	t[123] = "321";
	t[456] = "654";
	t[789] = "987";

	BOOST_CHECK_EQUAL( "321", t[123] );
	BOOST_CHECK_EQUAL( "654", t[456] );
	BOOST_CHECK_EQUAL( "987", t[789] );

	BOOST_CHECK_EQUAL( sizeof( std::vector< int > ) + sizeof( int ), sizeof( Resolver::Table ) );
	}
}

void TestCastOperator()
{
	using namespace test_gs_fakes;

	ExtBusiness ebsns( "Hello" );
	BusinessType & bsns = ebsns;
	std::string info = ebsns;
	BOOST_CHECK_EQUAL( bsns.getBusinessInfo(), info );

	ebsns = std::string( "Hello world" );
	BOOST_CHECK_EQUAL( std::string( "Hello world" ), ebsns.getBusinessInfo() );
}

namespace TestPtr2ref_NS
{

class A
{
public:
	A( int a )
		: a( a )
	{
	}
	virtual ~A() {}
	virtual int getA()
	{
		return a;
	}
	virtual void erase()
	{
		a = 0;
	}
private:
	int a;
};

class B
{
public:
	B( A & a, int i0, int i1 )
		: a( a )
		, i0( i0 )
		, i1( i1 )
	{
	}
	A & getA()
	{
		return a;
	}
private:
	A & a;
	int i0;
	int i1;
};

typedef PtrAccessOpSimulator< B > BPtr;

class C
{
public:
	C( int i0, int i1, int i2 )
		: a0( i0 )
		, a1( i1 )
		, a2( i2 )
	{
	}
	C()
		: a0( 0 )
		, a1( 0 )
		, a2( 0 )
	{
	}
	BPtr get0()
	{
		return BPtr( B( a0, 0, 0 ) );
	}
	BPtr get1()
	{
		return BPtr( B( a1, 1, 1 ) );
	}
	BPtr get2()
	{
		return BPtr( B( a2, 2, 2 ) );
	}
private:
	A a0;
	A a1;
	A a2;
};

template< typename VecT >
std::string avec2str( const VecT & v )
{
	std::stringstream buf;
	for ( typename VecT::const_iterator it = v.begin(); it != v.end(); ++it )
	{
		buf << ( * it )->getA() << ", ";
	}
	return buf.str();
}

}

void TestPtr2ref()
{
	using namespace GRAPH_SUPPORT_NS::utils;
	using namespace TestPtr2ref_NS;

	C ccoll[] = { C( 1, 2, 3 ), C( 2, 3, 4 ), C( 3, 4, 5 ) };
	std::vector< C > cvec = arr2vector< C >( ccoll );
	BOOST_CHECK_EQUAL( 3, cvec.size() );
	BOOST_CHECK_EQUAL( 2, cvec[0].get1()->getA().getA() );
	BOOST_CHECK_EQUAL( 3, cvec[1].get1()->getA().getA() );
	BOOST_CHECK_EQUAL( 4, cvec[2].get1()->getA().getA() );

	std::for_each( cvec.begin(), cvec.end(),
			boost::bind( //	elem.get1()->getA().erase();
					& A::erase, boost::bind( & B::getA, boost::bind(
							& ptr2ref< B, BPtr >, boost::bind( & C::get1, _1 ) ) ) ) );

	// TODO: include "findEdge" in UnifiedTest
	BOOST_CHECK_EQUAL( 0, cvec[0].get1()->getA().getA() );
	BOOST_CHECK_EQUAL( 0, cvec[1].get1()->getA().getA() );
	BOOST_CHECK_EQUAL( 0, cvec[2].get1()->getA().getA() );

	std::vector< boost::shared_ptr< A > > shpAvec;
	shpAvec.push_back( boost::shared_ptr< A >( new A( 123 ) ) );
	shpAvec.push_back( boost::shared_ptr< A >( new A( 234 ) ) );
	shpAvec.push_back( boost::shared_ptr< A >( new A( 345 ) ) );
	BOOST_CHECK_EQUAL( std::string( "123, 234, 345, " ), avec2str( shpAvec ) );

	std::for_each( shpAvec.begin(), shpAvec.end(),
			boost::bind( & A::erase, boost::bind( & ptr2ref< A, boost::shared_ptr< A > >, _1 ) ) );
	BOOST_CHECK_EQUAL( std::string( "0, 0, 0, " ), avec2str( shpAvec ) );

	shpAvec.push_back( boost::shared_ptr< A >( new A( 456 ) ) );
	shpAvec.push_back( boost::shared_ptr< A >( new A( 567 ) ) );
	std::vector< A * > pAvec;
	for ( std::vector< boost::shared_ptr< A > >::iterator it = shpAvec.begin(); it != shpAvec.end(); ++it )
	{
		pAvec.push_back( it->get() );
	}
	BOOST_CHECK_EQUAL( std::string( "0, 0, 0, 456, 567, " ), avec2str( pAvec ) );

	std::for_each( pAvec.begin(), pAvec.end(),
			boost::bind( & A::erase, boost::bind( & ptr2ref< A, A * >, _1 ) ) );
	BOOST_CHECK_EQUAL( std::string( "0, 0, 0, 0, 0, " ), avec2str( pAvec ) );
}

namespace TestRTTIStorageTemplate_NS
{
	class StructA
	{
	public:
		StructA( int field0, const std::string & field1 )
			: field0( field0 )
			, field1( field1 )
		{
		}
		int field0;
		std::string field1;
	};

	class StructB
	{
	public:
		StructB( double field2 )
			: field2( field2 )
		{
		}
		double field2;
	};
}

void TestRTTIStorageTemplate()
{
	using namespace TestRTTIStorageTemplate_NS;

	StructA a0( 123, "123" );
	BOOST_CHECK_EQUAL( 123, a0.field0 );
	BOOST_CHECK_EQUAL( std::string( "123" ), a0.field1 );

	StructB b0( 456.789 );
	BOOST_CHECK_EQUAL( 456.789, b0.field2 );

	RTTIStorage< StructA > sa0( & a0 );
	IRTTIStorageLabel * istorePtr = & sa0;
	IRTTIStorage< StructA > * sa0ptr = dynamic_cast< IRTTIStorage< StructA > * >( istorePtr );
	BOOST_CHECK( sa0ptr != NULL );
	BOOST_CHECK_EQUAL( & a0, sa0ptr->get() );
	BOOST_CHECK_EQUAL( 123, sa0ptr->get()->field0 );
	BOOST_CHECK_EQUAL( std::string( "123" ), sa0ptr->get()->field1 );

	RTTIStorage< StructB > sb0( & b0 );
	istorePtr = & sb0;
	sa0ptr = dynamic_cast< IRTTIStorage< StructA > * >( istorePtr );
	BOOST_CHECK( sa0ptr == NULL );
	IRTTIStorage< StructB > * sb0ptr = dynamic_cast< IRTTIStorage< StructB > * >( istorePtr );
	BOOST_CHECK( sb0ptr != NULL );
	BOOST_CHECK_EQUAL( & b0, sb0ptr->get() );
	BOOST_CHECK_EQUAL( 456.789, sb0ptr->get()->field2 );
	b0.field2 = -1;
	BOOST_CHECK_EQUAL( -1, sb0ptr->get()->field2 );
}

void TestRTTICopyStorageTemplate()
{
	using namespace TestRTTIStorageTemplate_NS;

	StructA a0( 123, "123" );
	BOOST_CHECK_EQUAL( 123, a0.field0 );
	BOOST_CHECK_EQUAL( std::string( "123" ), a0.field1 );

	RTTIStorage< StructA > sa0( & a0 );
	RTTICpStorage< StructA > cpsa0( a0 );
	IRTTIStorageLabel * labelPtrX = & sa0;
	IRTTIStorageLabel * labelPtrY = & cpsa0;
	IRTTIStorage< StructA > * sa0PtrX = dynamic_cast< IRTTIStorage< StructA > * >( labelPtrX );
	BOOST_CHECK( sa0PtrX != NULL );
	IRTTIStorage< StructA > * sa0PtrY = dynamic_cast< IRTTIStorage< StructA > * >( labelPtrY );
	BOOST_CHECK( sa0PtrY != NULL );
	IRTTIStorage< StructB > * sb0Ptr = dynamic_cast< IRTTIStorage< StructB > * >( labelPtrX );
	BOOST_CHECK( sb0Ptr == NULL );
	                                     sb0Ptr = dynamic_cast< IRTTIStorage< StructB > * >( labelPtrY );
	BOOST_CHECK( sb0Ptr == NULL );

	BOOST_CHECK_EQUAL( & a0, sa0PtrX->get() );
	BOOST_CHECK_NE( & a0, sa0PtrY->get() );
	BOOST_CHECK_EQUAL( 123, sa0PtrX->get()->field0 );
	BOOST_CHECK_EQUAL( 123, sa0PtrY->get()->field0 );
	a0.field0 = 456;
	BOOST_CHECK_EQUAL( 456, sa0PtrX->get()->field0 );
	BOOST_CHECK_EQUAL( 123, sa0PtrY->get()->field0 );
}
